import argparse
from datetime import datetime
import time
import subprocess
import re
import os
import sys
import multiprocessing
from multiprocessing import Pool
from multiprocessing import cpu_count
from concurrent.futures import ProcessPoolExecutor, as_completed

# 全局变量
global_times = 0
global_counts = 0
global_verbose = False

def log_verbose(message):
    """打印详细日志信息"""
    if global_verbose:
        print(f"[VERBOSE] {message}")

def replace_variables(command, times, counts):
    """替换命令中的变量占位符"""
    command = command.replace('${times}', str(times))
    command = command.replace('${counts}', str(counts))
    return command

def parse_duration(duration_str, last_execution_start_time):
    """解析持续时间字符串，计算到下次执行开始的等待时间"""
    units = {
        's': 1,
        'm': 60,
        'h': 3600
    }
    match = re.match(r'^(\d+(\.\d+)?)([smh])$', duration_str)
    if not match:
        raise ValueError(f"无效的时间格式: {duration_str}")

    value, unit = float(match.group(1)), match.group(3)
    target_interval = value * units[unit]
    
    if last_execution_start_time is not None:
        # 计算距离上次执行开始的时间间隔
        elapsed = (datetime.now() - last_execution_start_time).total_seconds()
        # 计算到下次执行开始应该等待的时间
        remaining = target_interval - elapsed
        if remaining > 0.1:  # 如果剩余时间大于100毫秒才等待
            return remaining
        else:
            # 如果已经超时或剩余时间很小，立即执行
            return 0
    else:
        # 第一次执行，返回完整的间隔时间
        return target_interval

def parse_time(time_str):
    """解析时间字符串（支持多种格式）为datetime.time对象"""
    import re

    # 使用正则表达式匹配时间格式
    pattern = r'^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$'
    match = re.match(pattern, time_str)

    if not match:
        raise ValueError(f"无效的时间格式: {time_str} (支持格式: HH:MM:SS, HH:MM, H:MM)")

    hour = int(match.group(1))
    minute = int(match.group(2))
    second = int(match.group(3)) if match.group(3) else 0

    # 验证时间范围
    if not (0 <= hour <= 23 and 0 <= minute <= 59 and 0 <= second <= 59):
        raise ValueError(f"时间值超出有效范围: {time_str}")

    return datetime.time(hour, minute, second)

def calculate_wait_seconds(target_time):
    """计算当前时间到目标时间点的等待秒数"""
    now = datetime.now()
    target_datetime = datetime.combine(now.date(), target_time)

    # 如果目标时间已过，则设置为第二天
    if target_datetime < now:
        target_datetime += datetime.timedelta(days=1)

    return (target_datetime - now).total_seconds()

def execute_single_command(args_tuple):
    """执行单个命令（用于多进程）"""
    command, times, counts = args_tuple

    # 替换变量
    processed_command = replace_variables(command, times, counts)

    try:
        log_verbose(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 进程 {os.getpid()} 执行命令: {processed_command}")
        result = subprocess.run(processed_command, shell=True, capture_output=True, text=True)

        return {
            'command': processed_command,
            'original_command': command,
            'pid': os.getpid(),
            'returncode': result.returncode,
            'stdout': result.stdout,
            'stderr': result.stderr,
            'success': result.returncode == 0
        }
    except Exception as e:
        return {
            'command': processed_command,
            'original_command': command,
            'pid': os.getpid(),
            'returncode': -1,
            'stdout': '',
            'stderr': str(e),
            'success': False
        }

def execute_commands(commands, multiprocess, max_workers):
    """执行命令列表"""
    global global_counts

    if not commands:
        return []

    if not multiprocess or len(commands) == 1:
        # 单进程执行
        processed_commands = []
        for command in commands:
            processed_command = replace_variables(command, global_times, global_counts)
            processed_commands.append(processed_command)
            global_counts += 1

        command_str = " && ".join(processed_commands)
        log_verbose(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 执行命令: {command_str}")
        result = subprocess.run(command_str, shell=True)
        if result.returncode != 0:
            print(f"命令执行失败，退出码: {result.returncode}")
        return [{'returncode': result.returncode, 'success': result.returncode == 0}]

    # 多进程执行
    print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 使用 {max_workers} 个进程并发执行 {len(commands)} 个命令")

    results = []

    # 准备命令参数，包含当前的times和counts值
    command_args = []
    for command in commands:
        command_args.append((command, global_times, global_counts))
        global_counts += 1

    try:
        with ProcessPoolExecutor(max_workers=max_workers) as executor:
            # 提交所有任务
            future_to_command = {executor.submit(execute_single_command, args): args[0] for args in command_args}

            # 收集结果
            for future in as_completed(future_to_command):
                try:
                    result = future.result()
                    results.append(result)

                    # 输出结果
                    if result['success']:
                        print(f"✓ 命令执行成功 (PID {result['pid']}): {result['command']}")
                        if result['stdout'].strip():
                            print(f"  输出: {result['stdout'].strip()}")
                    else:
                        print(f"✗ 命令执行失败 (PID {result['pid']}) 退出码 {result['returncode']}: {result['command']}")
                        if result['stderr'].strip():
                            print(f"  错误: {result['stderr'].strip()}")

                except Exception as e:
                    command = future_to_command[future]
                    print(f"✗ 命令执行异常: {command} - {e}")
                    results.append({
                        'command': command,
                        'returncode': -1,
                        'success': False,
                        'stderr': str(e)
                    })

    except KeyboardInterrupt:
        print("\n多进程执行被用户中断")
        raise

    # 输出执行总结
    successful = sum(1 for r in results if r['success'])
    failed = len(results) - successful
    log_verbose(f"\n执行完成: {successful} 个成功, {failed} 个失败")

    return results

def show_system_notification(notification, keep_seconds=1000):
    """使用系统原生方式显示通知"""
    if sys.platform == "win32":
        # Windows系统使用msg命令
        os.system(f'msg * /time:{keep_seconds} "{notification}"')
    elif sys.platform == "linux":
        # Linux系统使用notify-send
        os.system(f'notify-send "通知" "{notification}"')
    else:
        # MacOS或其他系统
        print(f"通知: {notification}")

def format_time_remaining(seconds):
    """格式化剩余时间显示"""
    if seconds < 60:
        return f"{int(seconds)}秒"
    elif seconds < 3600:
        minutes = int(seconds // 60)
        secs = int(seconds % 60)
        return f"{minutes}分{secs}秒"
    else:
        hours = int(seconds // 3600)
        minutes = int((seconds % 3600) // 60)
        secs = int(seconds % 60)
        return f"{hours}小时{minutes}分{secs}秒"

def wait_with_countdown(wait_seconds):
    """等待指定秒数，可选择显示倒计时"""
    global global_verbose
    # log_verbose(f"等待 {wait_seconds:.1f} 秒...")
    if global_verbose:
        # 显示详细倒计时
        print(f"开始倒计时，总计等待: {format_time_remaining(wait_seconds)}")
        start_time = time.time()
        end_time = start_time + wait_seconds
        try:
            while True:
                current_time = time.time()
                remaining = end_time - current_time
                if remaining <= 0:
                    break
                # 显示倒计时（不换行，覆盖上一行）
                time_str = format_time_remaining(remaining)
                progress_bar = create_progress_bar(wait_seconds - remaining, wait_seconds, 30)
                print(f"\r剩余时间: {time_str} {progress_bar}", end="", flush=True)
                time.sleep(0.1)  # 每100毫秒更新一次
        except KeyboardInterrupt:
            print(f"\n倒计时被用户中断")
            raise
        
        # 清除倒计时行
        print(f"\r倒计时完成！{' ' * 50}")  
    else:
        time.sleep(wait_seconds)

def create_progress_bar(current, total, width=30):
    """创建进度条"""
    if total == 0:
        return "[" + "=" * width + "]"

    progress = current / total
    filled = int(width * progress)
    bar = "=" * filled + "-" * (width - filled)
    percentage = int(progress * 100)
    return f"[{bar}] {percentage}%"

def main():
    global global_times
    global global_verbose

    parser = argparse.ArgumentParser(description="定时任务执行器")
    parser.add_argument('-d', '--duration', type=str, default='10m', help="持续时间（如100ms, 10s, 5m, 2h）")
    parser.add_argument('-t', '--time', type=str, help="执行时间点（格式HH:MM:SS）")
    parser.add_argument('-r', '--repeat_range', type=int, nargs='+', default=[0,1], help="循环执行次数[start,end]")
    parser.add_argument('-s', '--scripts', nargs='+', help="要执行的命令列表")
    parser.add_argument('-c', '--cancel_first_waiting', action='store_true', help="是否跳过第一帧等待直接开始执行")
    parser.add_argument('-n', '--notification', default='Warning', help="显示通知消息")
    parser.add_argument('-v', '--verbose', action='store_true', help="显示详细的倒计时进度")
    parser.add_argument('-m', '--multiprocess', action='store_true', help="启用多进程并发执行命令")
    parser.add_argument('--max_workers', type=int, default=cpu_count(), help="最大进程数（默认为CPU核心数，仅在多进程模式下有效）")

    args = parser.parse_args()
    global_verbose = args.verbose

    if args.notification and args.notification.lower() == 'none':
        args.notification = None

    # 如果没有提供命令或消息，则报错
    if not args.scripts and not args.notification:
        print("错误：必须提供要执行的命令(-s)或通知消息(-n)")
        return

    # 主执行循环
    execution_count = args.repeat_range[0]
    max_executions = args.repeat_range[1]
    last_execution_start_time = None  # 记录上次执行开始时间
    
    while execution_count < max_executions:
        global_times = execution_count
        print(f"开始第 {execution_count + 1}/{max_executions} 次执行 (times={global_times})")
        
        # 计算等待时间（基于上次执行开始时间）
        if args.duration:
            wait_seconds = parse_duration(args.duration, last_execution_start_time)
        elif args.time:
            target_time = parse_time(args.time)
            wait_seconds = calculate_wait_seconds(target_time)
        else:
            raise ValueError("未提供有效的持续时间或执行时间点")
        
        # 等待逻辑
        if args.cancel_first_waiting and execution_count == args.repeat_range[0]:
            print(f"跳过第一次等待，(times={global_times})")
        else:
            if wait_seconds > 0:
                wait_with_countdown(wait_seconds)

        # 记录本次执行开始时间（在执行命令之前记录）
        current_execution_start_time = datetime.now()
        
        # 执行命令或显示消息
        if args.scripts:
            execution_start = datetime.now()
            results = execute_commands(args.scripts, args.multiprocess, args.max_workers)
            execution_end = datetime.now()
            execution_duration = (execution_end - execution_start).total_seconds()
            
            # 检查是否有失败的命令
            # failed_commands = [r for r in results if not r['success']]
            # if failed_commands:
            #     print(f"警告：有 {len(failed_commands)} 个命令执行失败")
            
            log_verbose(f"命令执行耗时: {execution_duration:.2f} 秒")

        elif args.notification and (execution_count + 1) == args.repeat_range[1]:
            # 在通知消息中替换变量
            processed_notification = replace_variables(args.notification, global_times, global_counts)
            show_system_notification(processed_notification)
        else:
            raise ValueError("没有要执行的命令、消息或是输出格式错误")

        # 更新执行开始时间用于下次计算
        last_execution_start_time = current_execution_start_time
        execution_count += 1

        # 如果不是最后一次执行，需要重新计算等待时间
        if execution_count >= max_executions:
            break

    print(f"所有任务执行完成，共执行了 {execution_count} 次")


if __name__ == "__main__":
    # 在Windows上设置多进程启动方法
    if sys.platform == "win32":
        multiprocessing.set_start_method('spawn', force=True)

    try:
        main()
    except KeyboardInterrupt:
        print("\n程序已被用户中断")
